Dart eval:eval 方法
介绍
在《Dart eval:动态执行 Dart 代码》中的 demo 中,通过 dart_eval 的 eval 方法动态执行 Dart 代码。eval 内部是如何实现的?
注释
/// Evaluate the Dart [source] code. If the source is a raw expression such as "2 + 2" it will be evaluated directly
/// and the result will be returned; otherwise, the function [function] will be called with arguments specified
/// by [args]. You can use [plugins] to configure bridge classes.
/// You can also specify [outputFile] to write the generated EVC bytecode to a file.
执行 Dart 源代码。如果源码是一个原始表达式,比如 "2+2",会直接求值并返回对应结果。否则,会根据入参调用指定函数。你可以使用 Plugins 来配置桥接类。你也可以指定一个 outputFile,将生成的 EVC 代码写入到文件中。
方法签名
方法签名如下:
dynamic eval(String source,
{String function = 'main',
List args = const [],
List<EvalPlugin> plugins = const [],
@Deprecated('Use plugins instead') Function(Compiler)? compilerSettings,
@Deprecated('Use plugins instead') Function(Runtime)? runtimeSettings,
String? outputFile}) {
忽略废弃参数:
- source:Dart 源代码
- function:调用函数
- args:传入参数
- plugins:桥接插件
- outputFile:EVC 代码生成路径
初始化编译器
通过如下代码初始化编译器,并将编译器传入各个插件实现插件挂载:
final compiler = Compiler();
for (final plugin in plugins) {
plugin.configureForCompile(compiler);
}
初始化代码变量
入参 source 在内部会进行调整,创建一个 _source 变量进行承接:
var _source = source;
代码变换
根据一系列逻辑对代码进行变换:
if (!RegExp(r'(?:\w* )?' + function + r'\s?\([\s\S]*?\)\s?{').hasMatch(_source)) {
if (!_source.contains(';')) {
_source = '$_source;';
if (!_source.contains('return')) {
_source = 'return $_source';
}
}
_source = 'dynamic $function() {$_source}';
}
正则理解(借助 ChatGPT 帮我理解了这段正则):
- 概括:检测代码中是否不包括函数声明,其中 function 入参被拼接到字符串中
- 能够匹配
一些字符串 foo(a, b) {
一些字符串(a, b) {
- 能够匹配
- 具体解释:
r'(?:\w* )?'
:、- 括号内:
(?:)
表示一个非捕获组,它们在匹配时不会创建一个捕获组。在这里,非捕获组用于分组,但是不需要捕获组的值。- 然后是一个
\w*
,它匹配零个或多个字母、数字或下划线。 - 最后是一个空格,表示如果有函数名,那么它后面必须有一个空格。
- 括号外:
- 整个正则表达式中的问号(
?
)表示这个部分是可选的,即函数可能有名字,也可能没有名字。
- 整个正则表达式中的问号(
- 括号内:
r'\s?\([\s\S]*?\)\s?{'
\s
表示空格,首先是一个可选的空格- 然后是一个左圆括号(
\(
) - 接下来是对任意字符,包括空格和换行符(
[\s\S]
)的贪婪量词(*
),表示匹配零个或多个字符 - 量词后面的问号(
?
)表示使用非贪婪模式,这意味着它会尽可能少地匹配字符。 - 最后是一个右圆括号,然后又是一个可选的空格,最后是一个左大括号。
通过上述正则,判断代码中是否有 function 对应的函数签名:
- 如果有:
- 代码中是否有分号(
;
)- 没有分号:说明代码中不包含语句,_source 不做处理
- 没有分号:说明代码中包含语句,_source 做如下拼接:
_source = 'return $_source';
- 问题:这里为什么要做这种判断?
- 代码中是否有分号(
- 最后将代码封装到一个给定函数里:
_source = 'dynamic $function() {$_source}';
_source 实际值验证
上面 _source 的逻辑有点没理解,找几个实际例子验证一下:
"2 + 2":
dynamic main() {return 2 + 2;}
Cat('Fluffy') Demo:
class Cat {
Cat(this.name);
final String name;
String speak() {
return name;
}
}
String main() {
final cat = Cat('Fluffy');
return cat.speak();
}
可以看到:
- 2+2 中不包含函数声明,实际上直接进入
_source = 'dynamic $function() {$_source}';
- 第二个例子也没有命中上面的正则逻辑
- 原因是我给出的代码中已经包含 main 语句了
- 也就是说如果不包含会进入
了解规律后,使用下面代码执行:
print(eval("""
2+2
""", function: "hello"));
实际生成的 _source 为:
dynamic hello() {return 2+2
;}
原来,上面那段正则的含义是:在表达式 + 函数名场景下,自动将表达式封装到对应函数中。